package checkers3d.logic;

import checkers3d.presentation.GUIPictureBox;
import checkers3d.presentation.IInputObserver;
import checkers3d.presentation.ParticleSystem;
import checkers3d.presentation.RenderResourceBitmap;
import checkers3d.presentation.RenderResourceRectangle;
import checkers3d.presentation.UtilGUI;
import checkers3d.presentation.Vector;
import checkers3d.storage.DataManagerStats;
import checkers3d.storage.Logger;
import java.awt.Color;
import java.awt.Point;
import java.awt.Rectangle;
import java.util.LinkedList;
import javax.swing.JOptionPane;

/**
 *
 * @author Sean Keel
 * @author Ruben Acuna
 * @author Brenton Dong
 */
public class Game extends GameBase{

    private LinkedList<PlayingSurface3D> playingSurface3DReplays = new LinkedList<PlayingSurface3D>();

    /**
     * ParticleSystem for mine explosions.
     */
    ParticleSystem psExplosion;

    /**
     * ParticleSystem for first gameover emitter.
     */
    ParticleSystem psGameOver1;

    /**
     * ParticleSystem for second gameover emitter.
     */
    ParticleSystem psGameOver2;

    /**
     * ParticleSystem for third gameover emitter.
     */
    ParticleSystem psGameOver3;

    /**
     * ParticleSystem for forth gameover emitter.
     */
    ParticleSystem psGameOver4;

    /**
     * RR for gameover message.
     */
    RenderResourceBitmap gameOverRR;
    
    /**
     * GUIPictureBox used to display gameover message.
     */
    GUIPictureBox gameOver;

    /**
     * Flag set when game is over.
     */
    boolean winHasHappened = false;

    public Game(Player home, Player visitor, PlayingSurface3D boardSetup, Point size){
        super(home, visitor, visitor, boardSetup, size);

        //save an instant replay copy of the game before it begins.
        playingSurface3DReplays.addLast(playingSurface3D.clone());

        gameOverRR = new RenderResourceBitmap("message-gameover.png");
        gameOver = new GUIPictureBox((RenderResourceBitmap)null, null);
        gameOver.setDrawPosition(new Point((getSize().x - gameOverRR.getWidth()) / 2,
                                           (getSize().y - gameOverRR.getHeight()) / 2));
        
        home.setWinRR(ParticleSystem.LOCATION_PARTICLES + "player1-win1.png");
        visitor.setWinRR(ParticleSystem.LOCATION_PARTICLES + "player2-win1.png");

        psGameOver1 = new ParticleSystem(new Rectangle(0, getSize().y, 24, 24),
                                               new Vector(.3f, -1f),//velocity direction
                                               350f,                 //velocity magnitude
                                               new Vector(0, 100f),  //gravitational acceleration
                                               -1f,                  //angular momentum
                                               -.2f,                 //draw scale change
                                               0,                    //life span of particles
                                               96,                   //number of particles
                                               75,                   //delay between particle spawn
                                               false,
                                               this,
                                               home.getWinRR());
        psGameOver2 = new ParticleSystem(new Rectangle(72, getSize().y, 24, 24),
                                               new Vector(.3f, -1f),//velocity direction
                                               350f,                 //velocity magnitude
                                               new Vector(0, 100f),  //gravitational acceleration
                                               -1f,                  //angular momentum
                                               -.2f,                 //draw scale change
                                               0,                    //life span of particles
                                               96,                   //number of particles
                                               75,                   //delay between particle spawn
                                               false,
                                               this,
                                               home.getWinRR());
        psGameOver3 = new ParticleSystem(new Rectangle(getSize().x-72-24, getSize().y, 24, 24),
                                               new Vector(-.3f, -1f),//velocity direction
                                               350f,                 //velocity magnitude
                                               new Vector(0, 100f),  //gravitational acceleration
                                               1f,                   //angular momentum
                                               -.2f,                 //draw scale change
                                               0,                    //life span of particles
                                               96,                   //number of particles
                                               75,                   //delay between particle spawn
                                               false,
                                               this,
                                               home.getWinRR());
        psGameOver4 = new ParticleSystem(new Rectangle(getSize().x-24, getSize().y, 24, 24),
                                               new Vector(-.3f, -1f),//velocity direction
                                               350f,                 //velocity magnitude
                                               new Vector(0, 100f),  //gravitational acceleration
                                               1f,                   //angular momentum
                                               -.2f,                 //draw scale change
                                               0,                    //life span of particles
                                               96,                   //number of particles
                                               75,                   //delay between particle spawn
                                               false,
                                               this,
                                               home.getWinRR());

        psExplosion = new ParticleSystem(new Rectangle(400 - 12, 300 - 12, 24, 24),
                                               new Vector(0f, -.8f),//velocity direction
                                               25f,                 //velocity magnitude
                                               new Vector(0, -25f), //gravitational acceleration
                                               0f,                  //angular momentum
                                               .2f,                 //draw scale change
                                               0,                   //life span of particles
                                               32,                  //number of particles
                                               0,                   //delay between particle spawn
                                               false,
                                               this,
                                               new RenderResourceBitmap(ParticleSystem.LOCATION_PARTICLES + "smoke1.png"),
                                               new RenderResourceBitmap(ParticleSystem.LOCATION_PARTICLES + "smoke2.png"),
                                               new RenderResourceRectangle(2, 2, Color.ORANGE, Color.BLACK),
                                               new RenderResourceRectangle(2, 2, Color.RED, Color.BLACK),
                                               new RenderResourceRectangle(2, 2, Color.RED, Color.BLACK));

        add(gameOver);
    }

    public Game(Player home, Player visitor, Player current,
            PlayingSurface3D boardSetup, LinkedList savedreplays, Point size){
        this(home, visitor, boardSetup, size);

        this.playingSurface3DReplays = savedreplays;
        JOptionPane.showMessageDialog(null, ("The game has successfully been loaded."));
    }

    @Override
    public void onKeyPress(char key)
    {
        if((int)key == UtilGUI.KEY_ESCAPE)
            GameMaster.getInstance().addGUIContainer(new GameMenu(this,playerHome.getPlayer(),
                                                                  playerVisitor.getPlayer(),
                                                                  getCurrentPlayer(),
                                                                  playingSurface3D,
                                                                  playingSurface3DReplays,
                                                                  getSize()));
    }

    @Override
    public void onClick(Point position){
        super.onClick(position);

        if(getCurrentPlayerController() instanceof IInputObserver)
            ((IInputObserver)getCurrentPlayerController()).onClick(position);

        Point3D point3D = getCurrentPlayerController().popLastMove();

        if (point3D != null)
        {
            //the logic always assumes the player is facing upwards. the visitor
            //starts at top, so if it is there turn, flip the board for the
            //logic and transform the click into the flipped coordinates.
            if(getCurrentPlayerController() == playerVisitor)
            {
                playingSurface3D.flip();
                calculateClick(new Point3D(boardSizeX - point3D.x - 1,
                                           boardSizeY - point3D.y - 1,
                                           point3D.z));
                playingSurface3D.flip();
            }
            else
                calculateClick(point3D);
        }
    }

    /* Calls isValidMoveTarget for every position on the board.
     * If a target is found, a GUIPictureBox is made and stored into
     * the GUIContainer's list of drawables.
     *
     * @param p3d
     */
    private void calculateClick(Point3D p3d){

        if(playingSurface3D.isForced())
            clickCount++;

        Point3D initPos = findSelected();

        if(winner == null)
        {
            IBoardObjectDynamic piece = null;

            for(IBoardObject object : playingSurface3D.getPositionObjects(p3d))
                    if(object instanceof IBoardObjectDynamic){
                        piece = (IBoardObjectDynamic)object;
                        break;
                    }

            // We clicked a square that was highlighted as being a valid move.
            if(playingSurface3D.getPosition(p3d).getOverlay() == PlayingSurfacePositionOverlay.MOVEABLE)
            {
                movePiece(p3d);

                clearOverlays();

                // if king, advance the current piece state.
                if(playingSurface3D.getPositionDynamicObject(p3d) instanceof BoardObjectKing)
                {
                        if(pieceState == PieceState.UNUSED)
                            pieceState = PieceState.MOVED;
                        else
                            pieceState = PieceState.FINISHED;
                }else// if it is a normal checker, just finish the turn.
                    pieceState = PieceState.FINISHED;

                //if(pieceState != PieceState.MOVED)
                    playingSurface3D.setOverlay(PlayingSurfacePositionOverlay.FORCED_SELECTED, p3d);

                //Save a copy of the board once the move is complete. if the current
                //player is the visitor, the board is flipped. Unflip it so all the
                //replays have the same orientation.
                if(getCurrentPlayerController() == playerVisitor)
                {
                    playingSurface3D.flip();
                    playingSurface3DReplays.addLast(playingSurface3D.clone());
                    playingSurface3D.flip();
                }
                else
                    playingSurface3DReplays.addLast(playingSurface3D.clone());

                if(pieceState == PieceState.FINISHED)
                    cyclePlayerController();
                else
                {
                    calculateClick(p3d);
                }

            }
            //We click a square that was highlighted as being a valid warp.
            if(playingSurface3D.getPosition(p3d).getOverlay() == PlayingSurfacePositionOverlay.WARPABLE)
            {

                movePiece(p3d);

                // if king, advance the current piece state.
                if(playingSurface3D.getPositionDynamicObject(p3d) instanceof BoardObjectKing)
                {
                    if(pieceState == PieceState.UNUSED)
                        pieceState = PieceState.WARPED;
                    else
                        pieceState = PieceState.FINISHED;
                }else// if it is a normal checker, just finish the turn.
                    pieceState = PieceState.FINISHED;

                clearOverlays();

                playingSurface3D.setOverlay(PlayingSurfacePositionOverlay.FORCED_SELECTED, p3d);

                //Save a copy of the board once the move is complete. if the current
                //player is the visitor, the board is flipped. Unflip it so all the
                //replays have the same orientation.
                if(getCurrentPlayerController() == playerVisitor)
                {
                    playingSurface3D.flip();
                    playingSurface3DReplays.addLast(playingSurface3D.clone());
                    playingSurface3D.flip();
                }
                else
                    playingSurface3DReplays.addLast(playingSurface3D.clone());

                if(pieceState == PieceState.FINISHED)
                    cyclePlayerController();
                else
                {
                    calculateClick(p3d);
                }

            }
            //We clicked a square that was highlighted as being a valid jump.
            if(playingSurface3D.getPosition(p3d).getOverlay() == PlayingSurfacePositionOverlay.JUMPABLE)
            {
                movePiece(p3d);

                // if king, advance the current piece state.
                if(playingSurface3D.getPositionDynamicObject(p3d) instanceof BoardObjectKing)
                {
                    if(pieceState == PieceState.UNUSED)
                        pieceState = PieceState.MOVED;
                    else
                        pieceState = PieceState.FINISHED;
                }else// if it is a normal checker, just finish the turn.
                {
                    pieceState = PieceState.FINISHED;
                }

                clearOverlays();

                playingSurface3D.setOverlay(PlayingSurfacePositionOverlay.FORCED_SELECTED, p3d);

                if(getCurrentPlayerController() == playerVisitor)
                {
                    playingSurface3D.flip();
                    playingSurface3DReplays.addLast(playingSurface3D.clone());
                    playingSurface3D.flip();
                }
                else
                    playingSurface3DReplays.addLast(playingSurface3D.clone());

                if(buildJumpableOverlays(p3d)){//if there are any available jumps for the piece's new location.
                    clickCount=0;
                    calculateClick(p3d);//forces a click on the pieces new location.
                }
                else if(pieceState == PieceState.FINISHED)
                {
                    cyclePlayerController();
                }

            }
            //We clicked a square with one of our pieces.
            else if(!playingSurface3D.isForced() && piece != null && piece.isSelectableBy(getCurrentPlayer())
                    && playingSurface3D.getOverlay(p3d) != PlayingSurfacePositionOverlay.FORCED_SELECTED)
            {
                    buildOverlays(p3d, piece);
            }
            //Player chooses not to move twice
            else if(piece != null && piece.isSelectableBy(getCurrentPlayer()) && clickCount>1
                    && playingSurface3D.isForced())
            {
                cyclePlayerController();
            }
            //We are forced to click on a square since a jump is available after a successful jump
            else if(playingSurface3D.getOverlay(p3d) == PlayingSurfacePositionOverlay.FORCED_SELECTED)
            {
                if(!(playingSurface3D.getPositionDynamicObject(p3d) instanceof BoardObjectChecker))
                {
                    if(pieceState == PieceState.MOVED)
                        buildWarpableOverlays(p3d);
                    if(pieceState == PieceState.WARPED)
                    {
                        buildMoveableOverlays(p3d);
                        buildJumpableOverlays(p3d);
                    }
                }else
                    buildJumpableOverlays(p3d);
            }
            //We clicked on an empty square or one with enemy pieces only.
            else if(!playingSurface3D.isForced() && pieceState == PieceState.UNUSED)
            {
                clearOverlays();
            }
        }
    }

    private void movePiece(Point3D movePositon){
        IBoardObjectDynamic piece = null;
        Point3D piecePosition = null;

        //loop over all the PlayingSurfacePositions to find which one contains
        //the piece.
        for (int x = 0; x < boardSizeX; x++){
            for (int y = 0; y < boardSizeY; y++){
                for (int z = 0; z < boardSizeZ; z++){
                    Point3D target = new Point3D(x, y, z);

                    if(playingSurface3D.getOverlay(target) == PlayingSurfacePositionOverlay.SELECTED_PIECE
                            || playingSurface3D.getOverlay(target) == PlayingSurfacePositionOverlay.FORCED_SELECTED)
                        for(IBoardObject object : playingSurface3D.getPositionObjects(target))
                            if(object instanceof IBoardObjectDynamic){
                                piece = (IBoardObjectDynamic)object;
                                piecePosition = target;
                                break;
                            }
                }
            }
        }

        if(piece == null)
            throw new UnsupportedOperationException("Did not find SELECTED_PIECE. This should never happen.");

        Point3D relativeMovePosition = convertBoardXYZToObjectXYZ(movePositon, piecePosition);
        LinkedList<Point3D> path = piece.getPath(relativeMovePosition);
        boolean pieceKilled = false;
        
        //possible effects are currently: KINGIFY, NOTHING, PROTECTS, and REMOVE
        for(Point3D relativeStepPosition : path)
        {
            Point3D stepPosition = convertObjectXYZToBoardXYZ(relativeStepPosition, piecePosition);
            boolean protection = false;

            //HACK: I don't want to talk about it... :(
            for(IBoardObject object : playingSurface3D.getPositionObjects(stepPosition))
                if(object instanceof BoardObjectSafe)
                    if(object.getOwner() == piece.getOwner())
                        protection = true;

            //check all objects in this position for protection, if so, all
            //objects have protection for their time in this postion.
            for(IBoardObject occupant : playingSurface3D.getPositionObjects(stepPosition))
            {
                BoardObjectTouchEffect pieceEffect = piece.onTouch(occupant);
                BoardObjectTouchEffect occupantEffect = occupant.onTouch(piece);

                //calculate effect from the piece touching the occupant.
                if(pieceEffect == BoardObjectTouchEffect.PROTECTS)
                {
                    protection = true;
                }
                else if(pieceEffect == BoardObjectTouchEffect.REMOVE && !protection)
                {
                        //FIXME: only can kill first occupant because we must
                        //break out of loop so as not to cause iteratior error.
                        playingSurface3D.getPositionObjects(stepPosition).remove(occupant);
                        break;
                }
                else if(pieceEffect == BoardObjectTouchEffect.KINGIFY && !pieceKilled)
                {
                    //FIXME
                }

                //calculate effect from the occupant touching the piece.
                if(occupantEffect == BoardObjectTouchEffect.PROTECTS)
                {
                    protection = true;
                }
                else if(occupantEffect == BoardObjectTouchEffect.REMOVE && !protection)
                {
                    //create a new explosion at the current location.
                    Point location = convertPoint3DToPoint(stepPosition);
                    psExplosion.setEmitterRegion(new Rectangle(location.x, location.y,
                                                               TILE_SIZE, TILE_SIZE));
                    psExplosion.createAllParticles();

                    pieceKilled = true;
                    break;
                }
                else if(occupantEffect == BoardObjectTouchEffect.KINGIFY && !pieceKilled)
                {
                    IBoardObjectDynamic king = new BoardObjectKing(piece.getOwner(), piece.getOwner().getKingRR());
                    playingSurface3D.getPositionObjects(piecePosition).remove(piece);
                    playingSurface3D.getPositionObjects(piecePosition).add(king);
                    piece = king;
                    break;
                }

                //stop calculating effects for this step.
                if(pieceKilled)
                    break;
            }

            //stop calculating effects for this path.
            if(pieceKilled)
                break;
        }

        //remove piece from starting position.
        playingSurface3D.getPositionObjects(piecePosition).remove(piece);

        //add piece to move target if it wasn't killed.
        if(!pieceKilled)
            playingSurface3D.getPositionObjects(movePositon).add(piece);
    }

    private Point3D findSelected()
    {
        //loop over all the PlayingSurfacePositions to find which one contains
        //the piece.
        for (int x = 0; x < boardSizeX; x++){
            for (int y = 0; y < boardSizeY; y++){
                for (int z = 0; z < boardSizeZ; z++){
                    Point3D target = new Point3D(x, y, z);

                    if(playingSurface3D.getOverlay(target) == PlayingSurfacePositionOverlay.SELECTED_PIECE
                            || playingSurface3D.getOverlay(target) == PlayingSurfacePositionOverlay.FORCED_SELECTED)
                        for(IBoardObject object : playingSurface3D.getPositionObjects(target))
                            if(object instanceof IBoardObjectDynamic)
                                return target;
                }
            }
        }
        return null;
    }

    private void buildOverlays(Point3D p3d, IBoardObjectDynamic piece){
        clearOverlays();

        playingSurface3D.getPosition(p3d).setOverlay(PlayingSurfacePositionOverlay.SELECTED_PIECE);

        for (int x = 0; x < boardSizeX; x++){
            for (int y = 0; y < boardSizeY; y++){
                for (int z = 0; z < boardSizeZ; z++){
                    Point3D target = new Point3D(x, y, z);
                    Point3D relative = convertBoardXYZToObjectXYZ(target, p3d);

                    if(piece.isValidMoveTarget(playingSurface3D, p3d, target))
                        playingSurface3D.setOverlay(PlayingSurfacePositionOverlay.MOVEABLE, target);
                    if(piece.isValidWarpTarget(playingSurface3D, p3d, target))
                        playingSurface3D.setOverlay(PlayingSurfacePositionOverlay.WARPABLE, target);
                    if(piece.isValidJumpTarget(playingSurface3D, p3d, target))
                        playingSurface3D.setOverlay(PlayingSurfacePositionOverlay.JUMPABLE, target);
                }
            }
        }
    }

        private boolean buildMoveableOverlays(Point3D p3d){
        boolean availableJumps = false;

        IBoardObjectDynamic piece = null;
        Point3D piecePosition = null;

        //loop over all the PlayingSurfacePositions to find which one contains
        //the piece.
        for (int x = 0; x < boardSizeX; x++){
            for (int y = 0; y < boardSizeY; y++){
                for (int z = 0; z < boardSizeZ; z++){
                    Point3D target = new Point3D(x, y, z);

                    if(playingSurface3D.getOverlay(target) == PlayingSurfacePositionOverlay.SELECTED_PIECE
                            || playingSurface3D.getOverlay(target) == PlayingSurfacePositionOverlay.FORCED_SELECTED)
                        for(IBoardObject object : playingSurface3D.getPositionObjects(target))
                            if(object instanceof IBoardObjectDynamic){
                                piece = (IBoardObjectDynamic)object;
                                piecePosition = target;
                                break;
                            }
                }
            }
        }

//        clearOverlays();

        for (int x = p3d.x-1; x <= p3d.x+1; x = x+2){
            for (int y = p3d.y-1; y <= p3d.y+1; y = y+2){
                if(x>=0  && y>=0 && x<boardSizeX && y<boardSizeY){
                    Point3D target = new Point3D(x, y, p3d.z);
                    if(piece.isValidMoveTarget(playingSurface3D, p3d, target))
                        {
                            playingSurface3D.setOverlay(PlayingSurfacePositionOverlay.FORCED_SELECTED, p3d);
                            playingSurface3D.setOverlay(PlayingSurfacePositionOverlay.MOVEABLE, target);
                            availableJumps = true;
                        }
                }
            }
        }
        return availableJumps;
    }

        private boolean buildWarpableOverlays(Point3D p3d){
        boolean availableJumps = false;

        IBoardObjectDynamic piece = null;
        Point3D piecePosition = null;

        //loop over all the PlayingSurfacePositions to find which one contains
        //the piece.
        for (int x = 0; x < boardSizeX; x++){
            for (int y = 0; y < boardSizeY; y++){
                for (int z = 0; z < boardSizeZ; z++){
                    Point3D target = new Point3D(x, y, z);

                    if(playingSurface3D.getOverlay(target) == PlayingSurfacePositionOverlay.SELECTED_PIECE
                            || playingSurface3D.getOverlay(target) == PlayingSurfacePositionOverlay.FORCED_SELECTED)
                        for(IBoardObject object : playingSurface3D.getPositionObjects(target))
                            if(object instanceof IBoardObjectDynamic){
                                piece = (IBoardObjectDynamic)object;
                                piecePosition = target;
                                break;
                            }
                }
            }
        }

//        clearOverlays();

        for (int z = p3d.z-1; z <= p3d.z+1; z++){
                if(z>=0  && z<boardSizeZ){
                    Point3D target = new Point3D(p3d.x, p3d.y, z);
                    if(piece.isValidWarpTarget(playingSurface3D, p3d, target))
                        {
                            playingSurface3D.setOverlay(PlayingSurfacePositionOverlay.FORCED_SELECTED, p3d);
                            playingSurface3D.setOverlay(PlayingSurfacePositionOverlay.WARPABLE, target);
                            availableJumps = true;
                        }
                }
        }
        return availableJumps;
    }

    private boolean buildJumpableOverlays(Point3D p3d){
        boolean availableJumps = false;

        IBoardObjectDynamic piece = null;
        Point3D piecePosition = null;

        //loop over all the PlayingSurfacePositions to find which one contains
        //the piece.
        for (int x = 0; x < boardSizeX; x++){
            for (int y = 0; y < boardSizeY; y++){
                for (int z = 0; z < boardSizeZ; z++){
                    Point3D target = new Point3D(x, y, z);

                    if(playingSurface3D.getOverlay(target) == PlayingSurfacePositionOverlay.SELECTED_PIECE
                            || playingSurface3D.getOverlay(target) == PlayingSurfacePositionOverlay.FORCED_SELECTED)
                        for(IBoardObject object : playingSurface3D.getPositionObjects(target))
                            if(object instanceof IBoardObjectDynamic){
                                piece = (IBoardObjectDynamic)object;
                                piecePosition = target;
                                break;
                            }
                }
            }
        }

        //The piece dissappeared mid-jump (Smart Mine)
        if(piece == null)
            return false;

        for (int x = p3d.x-2; x <= p3d.x+2; x = x+4){
            for (int y = p3d.y-2; y <= p3d.y+2; y = y+4){
                if(x>=0  && y>=0 && x<boardSizeX && y<boardSizeY){
                    Point3D target = new Point3D(x, y, p3d.z);
                    if(piece.isValidJumpTarget(playingSurface3D, p3d, target))
                        {
                            playingSurface3D.setOverlay(PlayingSurfacePositionOverlay.FORCED_SELECTED, p3d);
                            playingSurface3D.setOverlay(PlayingSurfacePositionOverlay.JUMPABLE, target);
                            availableJumps = true;
                        }
                }
            }
        }
        return availableJumps;
    }

    /* object xy coordinate system:
     *  (-1, 1) ( 0, 1) ( 1, 1)
     *  (-1, 0)    O    ( 1, 0)
     *  (-1,-1) ( 0,-1) ( 1,-1)
     *
     * board xy coordinate system:
     *  ( 0, 0) ( 1, 0) ( 2, 0)
     *  ( 0, 1)    O    ( 2, 1)
     *  ( 0, 2) ( 1, 2) ( 2, 2)
     */
    private Point3D convertBoardXYZToObjectXYZ(Point3D targetPositon, Point3D objectPosition)
    {
        int x = -(objectPosition.x - targetPositon.x);
        int y =  (objectPosition.y - targetPositon.y);
        int z = -(objectPosition.z - targetPositon.z);

        return new Point3D(x, y, z);
    }

    private Point3D convertObjectXYZToBoardXYZ(Point3D relativePositon, Point3D objectPosition)
    {
        int x = (relativePositon.x + objectPosition.x);
        int y = (objectPosition.y - relativePositon.y);
        int z = (relativePositon.z + objectPosition.z);

        return new Point3D(x, y, z);
    }

    public Player detectWinner(){
        if(playingSurface3D.getSurfaceDynamicsAmount(playerVisitor.getPlayer()) == 0)
            return playerHome.getPlayer();
        else if(playingSurface3D.getSurfaceDynamicsAmount(playerHome.getPlayer()) == 0)
            return playerVisitor.getPlayer();
        else
            return null;
    }

    /**
     * This is used to forfeit the game
     */
    public void setWinner(Player player){
        winner = player;
    }

    protected void cyclePlayerController(){
        
        pieceState = PieceState.UNUSED;
        clickCount = 0;
 
        nextPlayerController();

        //if the new IPlayerController doesn't need mouse input, perform the
        //next move it wants to make. then cycle again.
        if(!(getCurrentPlayerController() instanceof IInputObserver)){
            //we assume that the controller requested a valid move and has
            //prepped the PlayingSurface3D.
            calculateClick(getCurrentPlayerController().popLastMove());
        }
    }

    /**
     * Returns the screen coordinates of a Point3D.
     *
     * @param p3d A Point3D.
     * @return Screen coordinates of Point3D.
     */
    private Point convertPoint3DToPoint(Point3D p3d)
    {
        Point screenLocation = new Point();
        Point3D p3dTransformed;

        if(getCurrentPlayer() == playerHome.getPlayer())
            p3dTransformed = p3d;
        else
            p3dTransformed = new Point3D(boardSizeX - p3d.x - 1, boardSizeY - p3d.y - 1, p3d.z);

        screenLocation.x = p3dTransformed.x * TILE_SIZE + boardLocations[p3d.z].x;
        screenLocation.y = p3dTransformed.y * TILE_SIZE + boardLocations[p3d.z].y;

        return screenLocation;
    }

   /**
     * Informs the particle systems that a given amount of time in milliseconds
     * has elapsed. Used for animation.
     *
     * @param ms Milliseconds elapsed.
     */
    @Override
    public void tick(int ms)
    {
        if(winner == null)
            winner = detectWinner();

        if(winner != null && !winHasHappened)
        {
            //update statistics
            try
            {
                Player loser;

                if(winner == playerHome.getPlayer())
                    loser = playerVisitor.getPlayer();
                else
                    loser = playerHome.getPlayer();

                DataManagerStats.updateInidivualStatistic(winner, true, false,false);
                DataManagerStats.updateInidivualStatistic(loser, false, true, false);
                DataManagerStats.updateOponentStats(winner, loser, true, false,false);
                DataManagerStats.updateOponentStats(loser, winner, false,true,false);
            }
            catch(Exception e)
            {
                Logger.log("Error in Game trying to use DataManaterStats");
            }

            gameOver.setRenderResource(gameOverRR);
            psGameOver1.setRenderResources(winner.getWinRR());
            psGameOver2.setRenderResources(winner.getWinRR());
            psGameOver3.setRenderResources(winner.getWinRR());
            psGameOver4.setRenderResources(winner.getWinRR());
            psGameOver1.setContinuous(true);
            psGameOver2.setContinuous(true);
            psGameOver3.setContinuous(true);
            psGameOver4.setContinuous(true);

            winHasHappened = true;
        }

        try{
            super.tick(ms);

            //update all particle systems.
            psExplosion.tick(ms);
            psGameOver1.tick(ms);
            psGameOver2.tick(ms);
            psGameOver3.tick(ms);
            psGameOver4.tick(ms);
        } catch(NullPointerException e){
            // Ignore it.. for now.
        }
    }

}